/* 
 * File:   BlockLinkedList.h
 * Author: brady
 *
 * Created on February 6, 2011, 3:09 PM
 */

#include "Disk.h"
#include "Block.h"
#include "CannotReadException.h"

#ifndef BLOCKLINKEDLIST_H
#define	BLOCKLINKEDLIST_H


/*
 * Apparently every method in this Class is supposed to be inlined.
 * so no .cpp will be needed.
 * -Brady
 */
class BlockLinkedList {
public:
    /*Default initialization for all data members. Does not modify the Disk. */
    BlockLinkedList()
    {
        //Brady will be handling all the Constructors
        this->m_blockSize = Disk::DEFAULT_BLOCK_SIZE;
        this->m_startBlockNumber = -1;
        this->m_endBlockNumber = -1;
        this->m_numberOfBlocks = 0;
        this->m_disk = NULL;
        this->m_seek = NULL;

    }

    /*
     * Uses the disk and block size given to
     * initialize those data members of the object.
     * Other data members get default initialization. Does not modify the Disk.
     * Parameters:
     * disk 	to use in operations on the BlockLinkedList thus constructed.
     * blockSize 	size of the blocks in this BlockLinkedList.
     */
    BlockLinkedList (Disk *disk, int blockSize)
    {
        this->m_disk = disk;
        this->m_blockSize = blockSize;
        this->m_startBlockNumber = -1;
        this->m_endBlockNumber = -1;
        this->m_numberOfBlocks = 0;
        this->m_seek = NULL;
        
    }

    /*
     * Create a new BlockLinkedList using the disk,
     * and block size of another BlockLinkedList.
     * Does not modify the Disk.
     * Parameters:
     * b Data member values are copied over from this other BlockLinkedList.
     */
    BlockLinkedList (BlockLinkedList *b)
    {
        this->m_disk = b->m_disk;
        this->m_blockSize = b->m_blockSize;
        this->m_startBlockNumber = b->m_startBlockNumber;
        this->m_endBlockNumber = b->m_endBlockNumber;
        this->m_numberOfBlocks = b->m_numberOfBlocks;
        this->m_seek = b->m_seek;
    }

    /*
     * default copy constructor
     */
    BlockLinkedList(const BlockLinkedList& orig)
    {

    }

    /*Default Destructor*/
    ~BlockLinkedList()
    {
        this->m_startBlockNumber = -1;
        this->m_endBlockNumber = -1;
        this->m_numberOfBlocks = 0;
        this->m_seek = NULL;
    }

    /*
     * Generates a one block BlockLinkedList.
     * Modifies the disk.
     * Assumes the disk and block size data members of this BlockLinkedList
     * have been properly initialized.
     * Parameters:
     *     	blockNumber 	is the block number on disk of the first
     * (and only) block of this BlockLinkedList.
     * Resets the data in the m_buffer of this block before writing to disk.
     */
    bool Initialize (int blockNumber)
    {
      //Sean will be writing this
    
      delete m_seek;
      m_seek = new Block(blockNumber, Disk::DEFAULT_BLOCK_SIZE);
      m_seek->SetPointer(-1,0);
      m_startBlockNumber = blockNumber;
      m_endBlockNumber = blockNumber;
      m_numberOfBlocks = 1;
      return m_seek->Write(m_disk);
    }

    /*
     * Returns the amount of "usable" data within each block on disk.
     * Returns: the block size in bytes of the blocks
     * associated with the BlockLinkedList.
     * The returned value will be 4 bytes less than the block size of the Disk
     * associated with this BlockLinkedList because the first four bytes of
     * each block's m_buffer are reserved for the "next pointer".
     */
    int GetBlockSize ()
    {
        //Brady
        return this->m_blockSize - sizeof(int); //int is four bytes
    }

    /*Return the number of blocks currently in this BlockLinkedList. */
    int GetNumberOfBlocks ()
    {
        return this->CountBlocks();
    }

    Disk* getDisk(){
        return this->m_disk;
    }
    /*
     * Unlinks the block that is the starting block of this BlockLinkedList.
     * Does not make changes on disk.
     * Returns:
     * the Block representing the unlinked block.
     * This Block isn't linked into any BlockLinkedList.
     * The next pointer of the Block will still reference the Block
     * to which it was previously linked. It is the responsibility of
     * the caller to deallocate the returned Block.
     */
   virtual Block * UnlinkBlock ()
    {

        //go to the first block in list
        Rewind();
        if (this->GetCurrentBlock()->GetBlockNumber() == -1){
            return this->GetCurrentBlock();
        }
        //get the pointer to next block
        int next_pointer = this->GetCurrentBlock()->GetPointer(0);
        //make a new block pointing to the same next block;
        Block* unlinked_block;
        unlinked_block = new Block(this->GetCurrentBlock()->GetBlockNumber(), this->m_disk);
        //point this list to the next block;
        this->m_startBlockNumber = next_pointer;
        //return the unlinked block
        Rewind();
        return unlinked_block;
        
    }

    /*
     * Add another block to the end of this BlockLinkedList.
     * Makes changes on disk.
     * Assumes that the ending block number for this BlockLinkedList is valid.
     * Parameters:
     * newBlock 	is the new Block.
     * This block should have already been removed from the BlockLinkedList
     * upon which it was previously. Resets the data in the m_buffer of this
     * block before writing to disk.
     */
    bool AddBlock (Block *newBlock)
    {
        m_seek = new Block(m_endBlockNumber, m_disk);
        m_seek->SetPointer(newBlock->GetBlockNumber(), 0);
        if(!m_seek->Write(m_disk)) return false;
        newBlock->SetPointer(-1,0);
        m_numberOfBlocks++;
        m_endBlockNumber = newBlock->GetBlockNumber();
        return newBlock->Write(m_disk);
    }

    /*Return the starting block number for the BlockLinkedList. */
    int GetStartBlockNumber ()
    {
        //Brady
        return this->m_startBlockNumber;
    }

    /*Return the ending block number for the BlockLinkedList. */
    int GetEndBlockNumber ()
    {
        //Brady

//        Rewind();
//        while(m_seek->GetPointer(0) != -1)
//        {
//            GetNextBlock();
//        }
        return this->m_endBlockNumber;
    }

    /*
     * After this call, GetCurrentBlock will return the first block
     *  in the BlockLinkedList. After this call, a call to GetNextBlock
     *  will seek to the second Block (if any) in the BlockLinkedList.
     * No changes are made to disk.
     */
    void Rewind ()
    {
        if(this->m_startBlockNumber == -1) return; //don't rewind an empty list
	delete this->m_seek;
	this->m_seek = new Block(this->m_startBlockNumber, this->m_disk);
    }


    /*Returns a reference to the current Block in the
     * Rewind/GetNextBlock sequence.
     */
    Block * GetCurrentBlock ()
    {
        //Brady is working on this
        if(m_startBlockNumber ==-1){
            m_seek = new Block(-1,Disk::DEFAULT_BLOCK_SIZE);
            m_seek->SetBlockNumber(-1);
            m_seek->SetPointer(-1,0);       
        }
        return this->m_seek;
    }

    /*
     * Seeks the BlockLinkedList to the next block,
     * unless GetCurrentBlock() returns null.
     * Changes the value returned by GetCurrentBlock()
     * to the next Block in the file, unless no more Blocks exist.
     * In this case, GetCurrentBlock() will now return null.
     * No changes are made to disk.
     */
    void GetNextBlock ()
    {
        Block* current_block = GetCurrentBlock();
        if(current_block->GetBlockNumber() == -1) return; //there is no block in this list
        int next_pointer = GetCurrentBlock()->GetPointer(0);
        if(next_pointer == -1) return; //the current block is the last block
        Block* next_block = new Block(next_pointer, this->m_disk);
	delete this->m_seek;
        this->m_seek = next_block;
    }

    /*
     * Outputs to the console the sequence of block numbers comprising
     * this BlockLinkedList.
     * Rewind state is changed by this call.
     */
    void Output ()
    {
        Rewind();
        if(this->m_startBlockNumber == -1)
        {
            cout << "<List is empty>\n";
            return;
        }
        while(1){
            //Block exists, output its number
            cout << GetCurrentBlock()->GetBlockNumber() <<" "<< GetCurrentBlock()->GetPointer(0)<< " "
                    <<GetCurrentBlock()->GetBuffer()[4] << "\n";
            //see if a next block exists
            int next_pointer = GetCurrentBlock()->GetPointer(0);
            //if it doesn't exist, add a newline and return
            if(next_pointer == -1)
            {
                cout << "\n";
                return;
            }
            //if it does exist, seek to it and continue
            GetNextBlock();
        }	
    }

    /*
     * Replaces the m_buffer of the GetCurrentBlock()
     * Block on Disk of this BlockLinkedList with the m_buffer
     * of the block given as a parameter.
     * The first four bytes of the m_buffer of the blk parameter
     * are modified-- and replaced with the Disk block's next pointer.
     * The block number of the blk parameter is also modified.
     */
    bool Replace (Block *blk)
    {
        Block* to_replace = GetCurrentBlock();
	int nextptr = to_replace->GetPointer(0);
        //exit if current block does not exist
        if(to_replace->GetBlockNumber()== -1) return false;
        //take the current block and copy blk's buffer to it
        to_replace->SetBuffer(blk->GetBuffer());
        //copy the old pointer into the new block;
	to_replace->SetPointer(nextptr, 0);
        //the block should still point to the same next block
        return to_replace->Write(m_disk);
    }

    /*
     * Count the number of blocks in a BlockLinkedList by traversing
     *  the Blocks from the start Block to the end Block.
     * This may be useful for debugging.
     * Changes the Rewind state of the BlockLinkedList.
     */
    int CountBlocks ()
    {
        Block* prev_m_seek = m_seek;

        int count = 0;
        
        if (m_startBlockNumber != -1)
        {
        m_seek = new Block(m_startBlockNumber, m_disk);
        count++;
        while(m_seek->GetPointer(0) != -1)
          {
	    Block* temp = m_seek;
              m_seek = new  Block(m_seek->GetPointer(0),m_disk);
              count++;
	      delete temp;
          }
        }
        this->m_seek = prev_m_seek;
        return count;
    }

    void lock(){
	pthread_mutex_lock(&freelistlock);
    }

    void unlock(){
	pthread_mutex_unlock(&freelistlock);
    }

    /*For testing I assume, no Doxygen comment on it*/
    static void Test ()
    {
    //test list creation
    Disk newDisk ("Disk1", 100, 512);
    BlockLinkedList newList = new BlockLinkedList(&newDisk, Disk::DEFAULT_BLOCK_SIZE);
    newList.Output();

    //test initialization
    newList.Initialize(1);
    newList.Output();

    //test AddBlock()
    Block newBlock1(2, Disk::DEFAULT_BLOCK_SIZE);
    newList.AddBlock(&newBlock1);
    newList.Output();

    //another AddBlock test
    Block newBlock2(45,Disk::DEFAULT_BLOCK_SIZE);
    newList.AddBlock(&newBlock2);
    newList.Output();

    //test CountBlocks()
    cout<<newList.CountBlocks()<<endl;

    //test GetNumberOfBlocks
    cout<<newList.GetNumberOfBlocks()<<endl;

    //test GetBlockSize()
    cout<<newList.GetBlockSize()<<endl;

    //test GetCurrentBlock()
    Block curBlock(Disk::DEFAULT_BLOCK_SIZE);
    curBlock = *newList.GetCurrentBlock();
    cout<<curBlock.GetBlockNumber()<<endl;

    //test Rewind()
    newList.Rewind();
    curBlock = *newList.GetCurrentBlock();
    cout<<curBlock.GetBlockNumber()<<endl;

    //test GetNextBlock()
    newList.GetNextBlock();
    curBlock = *newList.GetCurrentBlock();
    cout<<curBlock.GetBlockNumber()<<endl;

    //test Replace()
    Block newBlock3(20,Disk::DEFAULT_BLOCK_SIZE);
    newList.Replace(&newBlock3);
    newList.Output();

    //test UnlinkBlock()
    newList.UnlinkBlock();;
    newList.Output();

    //test GetStartBlockNumber()
    cout<<newList.GetStartBlockNumber()<<endl;
    
    //test GetEndBlockNumber()
    cout<<newList.GetEndBlockNumber()<<endl;

    cout<< "+++++++++++EMPTY LIST TESTS+++++++++++"<<endl<<endl<<endl;


    //more tests with an empty list
    Disk emptyDisk("Disk2",100, Disk::DEFAULT_BLOCK_SIZE);
    BlockLinkedList emptyList = new BlockLinkedList(&emptyDisk, Disk::DEFAULT_BLOCK_SIZE);

    //test CountBlocks()
    cout<<emptyList.CountBlocks()<<endl;

    //test GetNumberOfBlocks
    cout<<emptyList.GetNumberOfBlocks()<<endl;

    //test GetCurrentBlock()
    curBlock = *emptyList.GetCurrentBlock();
    cout<<curBlock.GetBlockNumber()<<endl;
 
    //test Rewind()
    emptyList.Rewind();
    curBlock = *emptyList.GetCurrentBlock();
    cout<<curBlock.GetBlockNumber()<<endl;

    //test GetNextBlock()
    emptyList.GetNextBlock();
    curBlock = *emptyList.GetCurrentBlock();
    cout<<curBlock.GetBlockNumber()<<endl;

    //test Replace()
    emptyList.Replace(&newBlock3);
    emptyList.Output();

    //test UnlinkBlock()
    emptyList.UnlinkBlock();;
    emptyList.Output();

    //test GetStartBlockNumber()
    cout<<emptyList.GetStartBlockNumber()<<endl;

    //test GetEndBlockNumber()
    cout<<emptyList.GetEndBlockNumber()<<endl;

    }
protected:
    Disk * 	m_disk;  //Data members
    int 	m_blockSize;
    int 	m_numberOfBlocks;
    int 	m_startBlockNumber;
    int 	m_endBlockNumber;
    Block * 	m_seek;
    Block *      emptyBlock;
    
pthread_mutex_t freelistlock;
private:

};


#endif	/* BLOCKLINKEDLIST_H */

